Python类中如何定义多个构造器方法重载和泛方法 | 您所在的位置:网站首页 › int 与float › Python类中如何定义多个构造器方法重载和泛方法 |
什么是“泛方法”? 由多个方法组成的方法,这些方法为不同的类型实现相同的操作。 举个栗子现在有个需求,需要你通过以下几种方式创建一个自定义的日期类(CustomDate): 时间戳 年、月、日(包含三个整数的元组) ISO 格式的字符串 Datetime 类 一般实现from datetime import date, datetime class CustomDate: def __init__(self, arg): if isinstance(arg, (int, float)): self.__date = date.fromtimestamp(arg) elif isinstance(arg, tuple) and len(arg) == 3 and all(map(lambda x: isinstance(x, int), arg): self.__date = date(*arg) elif isinstance(arg, str): self.__date = date.fromisoformat(arg) elif isinstance(arg, datetime): self.__date = datetime.date() else: raise TypeError("could not create instance from " + type(arg).__name__) @property def date(): return self.__date登录后复制注:这里暂不讨论传入的日期/时间戳合不合法,仅仅只对类型做大致判断。 有没有更好的方式?我们可以将不同的构建方式拆分为多个方法,并利用 functools 中的 singledispatchmethod 装饰器来根据传入的参数类型决定调用哪个方法。 from datetime import date, datetime from functools import singledispatchmethod class CustomDate: @singledispatchmethod def __init__(self, arg): raise TypeError("could not create instance from " + type(arg).__name__) @__init__.register(int) @__init__.register(float) def __from_timestamp(self, arg): self.__date = date.fromtimestamp(arg) @__init__.register(tuple) def __from_tuple(self, arg): if len(arg) == 3 and all(map(lambda x: isinstance(x, int), arg)): self.__date = date(*arg) else: raise ValueError("could not create instance from a malformed tuple") @__init__.register(str) def __from_isoformat(self, arg): self.__date = date.fromisoformat(arg) @__init__.register(datetime) def __from_datetime(self, arg): self.__date = arg.date() @property def date(self): return self.__date登录后复制这样一来,我们便能将每种参数类型的初始化独立成一个个的方法了。 缺点#1 单分派在调用期间应该使用哪个方法实现由分派算法决定。如果该算法只基于单个参数的类型来决定使用哪个方法实现,则称其为单分派。 singledispatchmethod 就是就是单分派的。也就是说,只有第一个参数会作为考量。这在实际业务中是远远不足的。 #2 不支持 typing然而,如上,对元组中元素类型判断还是需要我们用 if/else 实现。也就是说,我们不能使用 typing.Tuple[int, int, int]。 作为一种折中的方案,或许我们可以定义一个 ThreeIntTuple 类来对其进行限定,将这些判断从 CustomDate 类中隔离开来。 这里仅提供一个思路让大家参考,我就不实现了(因为我们有更好的方式 xD)。 替代方案:multimethod 库这个库不是标准库之一,需要通过 pip 安装: pip install multimethod登录后复制优势multimethod 采用的是多分派算法,能更好地满足更复杂的场景。此外,该库对 typing 中的类型也有不错的支持。 更更好的实践方式回到上面的问题,我们可以这么改进: 使用 multimethod 方法来替代 singledispatchmethod; 使用 Tuple[int, int, int] 来替代 tuple,不再需要手动校验元组的长度和元素类型了; from datetime import date, datetime from typing import Tuple, Union from multimethod import multimethod class CustomDate: @multimethod def __init__(self, arg): raise TypeError("could not create instance from " + type(arg).__name__) @__init__.register def __from_timestamp(self, arg: Union[int, float]): self.__date = date.fromtimestamp(arg) @__init__.register def __from_tuple(self, arg: Tuple[int, int, int]): self.__date = date(*arg) @__init__.register def __from_isoformat(self, arg: str): self.__date = date.fromisoformat(arg) @__init__.register def __from_datetime(self, arg: datetime): self.__date = arg.date() @property def date(self): return self.__date登录后复制究极好的实践方式(真正的方法重载)在此之前,先问大家一个简单的问题(这跟我们之后的内容有很大的联系): class A: def a(self): print(1) def a(self): print(2) A().a()登录后复制以上这段代码会输出什么?还是会抛出错误? 输出 2。 在 Python 中,如果定义了重名的方法,最后一个方法是会覆盖掉之前的方法的。 但你或许不知,我们可以通过元类(metaclass)来改变这一行为: class MetaA(type): class __prepare__(dict): def __init__(*args): pass def __setitem__(self, key, value): if self.get('a'): # Line 7 super().__setitem__('b', value) # Line 8 else: super().__setitem__(key, value) class A(metaclass=MetaA): def a(self): print(1) def a(self): print(2) A().a() # => 1 A().b() # => 2 # Line 22登录后复制在第 7 和第 8 行,我们将重名的 a 方法改名为 b,并在第 22 行成功地调用它了。 multimethod 的维护者们很好地运用了这一点,对重名的方法进行了处理,以达到一种“特殊的效果”。 回到正题,我们可以做出如下改进: 将 multimethod.multidata 设置为 CustomDate 类的元类; 将所有方法命名为 __init__。 from datetime import date, datetime from typing import Tuple, Union from multimethod import multimeta class CustomDate(metaclass=multimeta): def __init__(self, arg: Union[int, float]): self.__date = date.fromtimestamp(arg) def __init__(self, arg: Tuple[int, int, int]): self.__date = date(*arg) def __init__(self, arg: str): self.__date = date.fromisoformat(arg) def __init__(self, arg: datetime): self.__date = arg.date() def __init__(self, arg): raise TypeError("could not create instance from " + type(arg).__name__) @property def date(self): return self.__date登录后复制从效果上来看,这完全和静态语言的方法重载一模一样! 以上就是Python类中如何定义多个构造器方法重载和泛方法的详细内容,更多请关注php中文网其它相关文章! |
CopyRight 2018-2019 实验室设备网 版权所有 |